/**
 ******************************************************************************
 * @file touch_state.c
 * @author GX (2524913364@qq.com)
 * @brief 	触摸屏状态机：能识别单击、拖动、滑屏。(在以前的状态机 2.0.0 的基础上重写)
 * 					该模块需要用到队列内容，因此还需要与配套的队列文件一起使用 "tcState_queue.h"
 * 					
 * 					>>用户使用的接口
 * 						InitTouch_pthread()	创建一个线程初始化触摸屏状态机
 * 						touch_mode()				获取和设置状态机功能 @ref TOUCH_MODE_CONFIG
 * 						get_attr_touch() 		从状态队列中获取一个状态	
 * 					
 * 					在判断滑屏的时候也提供了两种算法供用户使用，通过宏的定义选择 TC_EXACT_SLIDE
 * 
 * @note		用户在退出时需要使用线程取消函数 pthread_cancel() 取消本线程，这样本模块申请，
 * 					的一些资源才能顺利释放掉
 * @version 3.0.0 
 * @date 2022-08-18
 * 
 * 
 ******************************************************************************
 *----------------------------------------------------------------------------*
 *  Remark         : Description                                              *
 *----------------------------------------------------------------------------*
 *  Change History :                                                          *
 *  <Date>     | <Version> | <Author>       | <Description>                   *
 *----------------------------------------------------------------------------*
 *  2022-08-18 | 3.0.0     | GX             | Create file                     *
 *----------------------------------------------------------------------------*
 *                                                                            *
 ******************************************************************************
 */


/*----------------------------- start of file -------------------------------*/
#include "touch_state.h"
 
static bool init_tc_stateMachine(void);
static void free_tc_stateMachine(void);
static void tc_cleanup_free(void *arg);		/* 线程取消例程函数释放资源 */
static void init_tcState(TouchState_t *tc);
static void renew_tcState(TouchState_t *tc);
static bool get_xy_touch(struct input_event tc_data, TouchState_t *tc);
static bool get_xy_last_touch(struct input_event tc_data, TouchState_t *tc);
static void maintenance_queue(void);

/**
 * @brief 配置功能的变量，对特定位置 1 可以打开特定功能
 * @ref 	TOUCH_MODE_CONFIG  in "touch_state.h"s
 */
static int8_t 	g_tc_mode = 0;

static int32_t  g_fd_ts;
static struct timeval  g_time_start;			
static struct timeval  g_time_cur;		
static TouchState_t 	 g_touch;								// 触摸屏状态机变量
static tcState_linkQueue_t *g_tcState_queue = NULL;	// 待响应状态队列
static pthread_mutex_t g_tcState_queue_mutex; // 线程使用变量针对 tcState_queue 时用的互斥锁 

/**
 * @brief 初始化触摸屏文件和创建触摸屏的线程
 * 
 * @return pthread_t 创建的触摸屏线程号
 */
pthread_t InitTouch_pthread(void)
{ 
  pthread_t child_id;
	gettimeofday(&g_time_start, NULL);
	// printf("time_start:%ld.%ld\n", g_time_start.tv_sec, g_time_start.tv_usec);

  //打开触摸屏 
	g_fd_ts = open(TOUCH_PATH, O_RDONLY);
	if(g_fd_ts == -1)
	{
		perror("open touch");
		return -1;
	}

  /* 将文件设置为非阻塞 */
  int32_t status = fcntl(g_fd_ts, F_GETFL);
  status |= O_NONBLOCK;
  fcntl(g_fd_ts, F_SETFL, status);

	int32_t ret = pthread_create(&child_id, NULL,Touch_stateMachine, NULL);
	if(ret < 0)
	{
		perror("pthread create error");
		return -1;
	}
	return child_id;
}

/**
 * @brief Get the attr touch object
 * 
 * @param tc [out] 保存的状态机属性的指针
 * @return ready_response true:用户获取的该状态是有效的可以使用
 * 												false:用户获取的状态是无效的，应该舍弃
 */
bool get_attr_touch(TouchState_t *tc)
{
	bool ready_response = false;
	if (!(g_tc_mode & TC_MODE_ONLYRET))
	{
		if (pthread_mutex_trylock(&g_tcState_queue_mutex) == 0)
		{
			tcState_datatype_t out_queue;
			ready_response = tcState_outQueue(g_tcState_queue, tc);
			pthread_mutex_unlock(&g_tcState_queue_mutex);
		}
	}
	else
	{
		if (pthread_mutex_trylock(&g_tcState_queue_mutex) == 0)
		{
			do
			{
				tcState_datatype_t out_queue;
				ready_response = tcState_outQueue(g_tcState_queue, tc);
			} while ((ready_response) && (tc->state < STATE_SINGLE_CLICK));
			pthread_mutex_unlock(&g_tcState_queue_mutex);
		}
	}
	return ready_response;
}

/**
 * @brief 控制状态机模式
 * 
 * @param cmd 	指令
 * 		@arg TC_GETMODE 获取当前状态机的模式,将当前状态存入第二个形参 mode 中
 * 		@arg TC_SETMODE 将 mode 中的值作为模式值
 * @param mode 	保存模式的值，该变量的作用根据 cmd 的不同而不同
 * @return true 
 * @return false 
 */
bool touch_mode(int32_t cmd, int8_t *mode)
{
	if (cmd == TC_GETMODE)
	{
		*mode = g_tc_mode;
	}
	else if ((cmd == TC_SETMODE) && (*mode < (1<<2)))
	{
		g_tc_mode = *mode;
	}
	else
	{
		return false;
	}
	return true;
}

/**
 * @brief 状态机的状态切换
 * 
 * @param arg 
 * @return void* 
 */
void *Touch_stateMachine(void *arg)
{
	bool get_xy = false;				// 成功读取完整坐标的标志（每次读只能读 x 或 y）
	struct input_event tc_data;

	if (!init_tc_stateMachine())
	{
		printf("  >> init state machine error! <<\n");
		return NULL;
	}

	/* 设置线程取消例程函数 */
	pthread_cleanup_push(tc_cleanup_free, NULL);

	while (1)
	{
		memset(&tc_data, 0, sizeof(struct input_event));
		int32_t ret = read(g_fd_ts, &tc_data, sizeof(struct input_event));
		if (ret != sizeof(struct input_event))
		{
			continue;
		}

		// gettimeofday(&g_time_cur, NULL);
	
		switch (g_touch.state)
		{
		case STATE_NOT_PRESSED:		/* 无操作 —— 过程态 */
			/* 获取按下瞬间的坐标值 */
			get_xy = get_xy_touch(tc_data, &g_touch);
			if (get_xy)
			{
				pthread_mutex_lock(&g_tcState_queue_mutex);
				tcState_inQueue(g_tcState_queue, &g_touch);
				pthread_mutex_unlock(&g_tcState_queue_mutex);
				g_touch.state = STATE_PRESSED;
				get_xy = false;
			}
			break;
		
		case STATE_PRESSED:				/* 按下 —— 过程态 */
			/* 松开触摸屏 */
			if((tc_data.type == EV_KEY) && (tc_data.code == BTN_TOUCH) 
					&& (tc_data.value == 0))
			{
				if (!get_xy)
				{
					g_touch.x_last = (g_touch.x_last == 0) ? g_touch.x : g_touch.x_last;
					g_touch.y_last = (g_touch.y_last == 0)  ? g_touch.y : g_touch.y_last;
				}
				g_touch.time_last = (tc_data.time.tv_sec - g_time_start.tv_sec)*1.0
														+ ((tc_data.time.tv_usec - g_time_start.tv_usec)*1.0/1000000);
				printf("  >> time:%f\ttime_last:%f\n", g_touch.time, g_touch.time_last);
				g_touch.state = STATE_RELEASE;
				get_xy = false;
				goto PRESSED_TO_RELEASE;	/* 直接跳转到释放状态 */
			}	

			/* 获取按住的坐标值 */
			get_xy = get_xy_last_touch(tc_data, &g_touch);
			if ((g_tc_mode & TC_MODE_DTAG) && (get_xy))
			{
				if ((g_touch.x != g_touch.x_last) || (g_touch.y != g_touch.y_last))
				{
					get_xy = false;		// 在切换状态前才重置为 false 
					g_touch.state = STATE_DTAG;
					pthread_mutex_lock(&g_tcState_queue_mutex);
					tcState_inQueue(g_tcState_queue, &g_touch);
					pthread_mutex_unlock(&g_tcState_queue_mutex);
				}
			}

			break;

		case STATE_RELEASE:				/* 释放 —— 过程态 */
			
			PRESSED_TO_RELEASE:			// 从按下状态直接跳转过来，判断结果态
			printf("(%d,%d)  (%d,%d)\n", g_touch.x, g_touch.y, g_touch.x_last, g_touch.y_last);

			/* 判断单击 */
			if ((g_touch.x_last == g_touch.x) && (g_touch.y_last == g_touch.y))
			{
				g_touch.state = STATE_SINGLE_CLICK;
				pthread_mutex_lock(&g_tcState_queue_mutex);
				tcState_inQueue(g_tcState_queue, &g_touch);
				pthread_mutex_unlock(&g_tcState_queue_mutex);
			}
			/* 判断滑屏 */
			else if (g_tc_mode & TC_MODE_SLIDE)
			{
				#ifdef TC_EXACT_SLIDE	
				float deltaX = (g_touch.x_last - g_touch.x)*1.0;		// x 坐标的偏差值
				float deltaY = (g_touch.y_last - g_touch.y)*1.0;		// y 坐标的偏差值
				float slope = deltaY/deltaX;												// 计算斜率

				/* 水平滑屏 */
				if ((slope < UPPER_BORDER_OF_HORIZONTAL_SLOPE) &&
						(slope > LOWER_BORDER_OF_HORIZONTAL_SLOPE))
				{
					/* 判断左还是右 */
					g_touch.direction = (deltaX > 0) ? TC_RIGHT : TC_LEFT;
					g_touch.state = STATE_SLIDE;
					
					/* 将状态入队 */
					pthread_mutex_lock(&g_tcState_queue_mutex);
					tcState_inQueue(g_tcState_queue, &g_touch);
					pthread_mutex_unlock(&g_tcState_queue_mutex);
				}
				/* 竖直滑屏 */
				if ((slope < LEFT_BORDER_OF_VERTICAL_SLOPE) ||
						(slope > RIGHT_BORDER_OF_VERTICAL_SLOPE))
				{
					/* 判断左还是右 */
					g_touch.direction = (deltaY > 0) ? TC_DOWN : TC_UP;
					g_touch.state = STATE_SLIDE;
					
					/* 将状态入队 */
					pthread_mutex_lock(&g_tcState_queue_mutex);
					tcState_inQueue(g_tcState_queue, &g_touch);
					pthread_mutex_unlock(&g_tcState_queue_mutex);
				}

				#else
				int32_t deltaX = (g_touch.x_last - g_touch.x > 0) ? 
													(g_touch.x_last - g_touch.x) : (g_touch.x - g_touch.x_last);
				int32_t deltaY = (g_touch.y_last - g_touch.y > 0) ? 
													(g_touch.y_last - g_touch.y) : (g_touch.y - g_touch.y_last);
				/* 左右滑屏 */
				if ((deltaX > deltaY) && (deltaX > 30))
				{
					/* 判断左还是右 */
					g_touch.direction = (g_touch.x_last > g_touch.x) ? TC_RIGHT : TC_LEFT;
					g_touch.state = STATE_SLIDE;
					
					/* 将状态入队 */
					pthread_mutex_lock(&g_tcState_queue_mutex);
					tcState_inQueue(g_tcState_queue, &g_touch);
					pthread_mutex_unlock(&g_tcState_queue_mutex);
				}
				/* 上下滑屏 */
				else if ((deltaX < deltaY) && (deltaY > 30))
				{
					/* 判断左还是右 */
					g_touch.direction = (g_touch.y_last > g_touch.y) ? TC_DOWN : TC_UP;
					g_touch.state = STATE_SLIDE;
					
					/* 将状态入队 */
					pthread_mutex_lock(&g_tcState_queue_mutex);
					tcState_inQueue(g_tcState_queue, &g_touch);
					pthread_mutex_unlock(&g_tcState_queue_mutex);
				}

				#endif /* TC_EXACT_SLIDE */
				
			}

			/* 将结果态入队之后将状态机重置 */
			renew_tcState(&g_touch);

			break;
		
		case STATE_DTAG:					/* 拖动 —— 结果态 */
			/* 松开触摸屏 */
			if((tc_data.type == EV_KEY) && (tc_data.code == BTN_TOUCH) 
					&& (tc_data.value == 0))
			{
				g_touch.state = STATE_RELEASE;
				get_xy = false;
				goto PRESSED_TO_RELEASE;	/* 直接跳转到释放状态 */
			}	

			/* 获取按住的坐标值 */
			get_xy = get_xy_last_touch(tc_data, &g_touch);
			if (get_xy)
			{
				get_xy = false;		// 在切换状态前才重置为 false 
				pthread_mutex_lock(&g_tcState_queue_mutex);
				tcState_inQueue(g_tcState_queue, &g_touch);
				pthread_mutex_unlock(&g_tcState_queue_mutex);
			}
			break;	
		
		default:
			break;
		}
		
		if (!(g_tc_mode & TC_MODE_NMQUEUE))
		{
			maintenance_queue();
		}
	}
	
	/* 线程取消历程函数弹栈前都会执行一次 */
	pthread_cleanup_pop(1);
}

/**
 * @brief 初始化整个状态机，包含状态、状态队列和互斥锁
 * 
 * @return true 
 * @return false 
 */
static bool init_tc_stateMachine(void)
{
	init_tcState(&g_touch);

	/* 初始化状态机待响应队列 */
	g_tcState_queue = tcState_createQueue();
	if (g_tcState_queue == NULL)
	{
		return false;
	}

	/* 初始化互斥锁 */
	if (pthread_mutex_init(&g_tcState_queue_mutex, NULL) != 0)
	{
		tcState_destroyQueue(g_tcState_queue);
		return false;
	}

	return true;
}

/**
 * @brief 释放状态机资源
 */
static void free_tc_stateMachine(void)
{
	/* 销毁状态机待响应队列 */
	tcState_destroyQueue(g_tcState_queue);

	/* 销毁互斥锁 */
	pthread_mutex_destroy(&g_tcState_queue_mutex);

	/* 关闭触摸屏文件 */
	close(g_fd_ts);
}

/**
 * @brief 线程接收到取消请求后会执行该线程历程函数
 * 
 * @param arg 
 */
static void tc_cleanup_free(void *arg)
{
	printf("\n\n [%s]释放资源 \n\n", __FUNCTION__);
	free_tc_stateMachine();
}

/**
 * @brief 初始化状态机的状态
 * 
 * @param tc 状态机结构体变量的地址
 */
static void init_tcState(TouchState_t *tc)
{
	/* 初始化触摸屏数据结构体 */
	// tc->response = true;	/* 初始化为已被响应 */
	tc->x_per = tc->y_per = 0;
	tc->x = tc->x_last = tc->y = tc->y_last = 0;
	tc->time_per = tc->time = tc->time_last = 0;
	tc->direction = TC_NONE;
	tc->state = STATE_NOT_PRESSED;
}

/**
 * @brief 复原状态机的状态，
 * 
 * @param tc 状态机结构体变量的地址
 */
static void renew_tcState(TouchState_t *tc)
{
	// tc->response = false;									/* 初始化未被响应 */
	tc->x_per = tc->x_last;
	tc->y_per = tc->y_last;
	tc->time_per = tc->time_last;

	tc->time =  tc->time_last = 0;
	tc->x = tc->x_last = tc->y = tc->y_last = 0;
	tc->direction = TC_NONE;
	tc->state = STATE_NOT_PRESSED;

	pthread_mutex_lock(&g_tcState_queue_mutex);
	tcState_inQueue(g_tcState_queue, tc);
	pthread_mutex_unlock(&g_tcState_queue_mutex);
}

/**
 * @brief 获取触摸屏的 x 或 y 坐标，调用一次只能获取其中一个(按下那一刻的坐标)
 * 
 * @param tc_data 			从触摸屏的文件中读取的内容结构体
 * @param tc 			[out]	将 tc_data 中的值存入 *tc 中
 * @return true	  x 和 y 都不为 0
 * @return false  x 和 y 其中一个为 0
 */
static bool get_xy_touch(struct input_event tc_data, TouchState_t *tc)
{
	/* 发生触屏事件，且为x轴事件 */
	if(tc_data.type==EV_ABS && tc_data.code==ABS_X)
	{
		tc->x = tc_data.value*800/1024;//*800/1024;
	}
	/* 发生触摸屏事件，且为y轴事件 */
	else if(tc_data.type==EV_ABS && tc_data.code==ABS_Y)
	{
		tc->y = tc_data.value*480/600;//*480/600;
	}

	if (tc->x != 0 && tc->y != 0)
	{
		tc->time = (tc_data.time.tv_sec - g_time_start.tv_sec)*1.0
									+ ((tc_data.time.tv_usec - g_time_start.tv_usec)*1.0/1000000);
		return true;
	}

	return false;
}

/**
 * @brief 获取触摸屏的 x 或 y 坐标，调用一次只能获取其中一个(按住时或者松开前的坐标)
 * 
 * @param tc_data 			从触摸屏的文件中读取的内容结构体
 * @param tc 			[out]	将 tc_data 中的值存入 *tc 中
 * @return true	  x 和 y 都不为 0
 * @return false  x 和 y 其中一个为 0
 */
static bool get_xy_last_touch(struct input_event tc_data, TouchState_t *tc)
{
	/* 发生触屏事件，且为x轴事件 */
	if(tc_data.type==EV_ABS && tc_data.code==ABS_X)
	{
		tc->x_last = tc_data.value*800/1024;//*800/1024;
	}
	/* 发生触摸屏事件，且为y轴事件 */
	else if(tc_data.type==EV_ABS && tc_data.code==ABS_Y)
	{
		tc->y_last = tc_data.value*480/600;//*480/600;
	}

	if (tc->x_last != 0 && tc->y_last != 0)
	{
		tc->time_last = (tc_data.time.tv_sec - g_time_start.tv_sec)*1.0
										+ ((tc_data.time.tv_usec - g_time_start.tv_usec)*1.0/1000000);
		printf("time:%f\ttime_last:%f\n", tc->time, tc->time_last);
		return true;
	}

	return false;
}

/**
 * @brief 维护队列，将过期的状态从队列中剔除
 */
static void maintenance_queue(void)
{
	if (pthread_mutex_trylock(&g_tcState_queue_mutex) == 0)
	{
		if (g_tcState_queue->size > 0)
		{
			gettimeofday(&g_time_cur, NULL);
			float time_cur = (g_time_cur.tv_sec - g_time_start.tv_sec)*1.0
												+ ((g_time_cur.tv_usec - g_time_start.tv_usec)*1.0/1000000);
			if (time_cur - g_tcState_queue->rear->data->time_last > 2)
			{
				tcState_datatype_t del_node;
				tcState_outQueue(g_tcState_queue, &del_node);
			}
		}
		pthread_mutex_unlock(&g_tcState_queue_mutex);
	}
}


/**
 * @brief 用户响应完状态之后需调用该函数设置响应状态
 * 
 */
// void response_over(void)
// {
// 	pthread_mutex_trylock(&g_tcState_mutex);
// 	g_touch.response = true;
// 	pthread_mutex_unlock(&g_tcState_mutex);
// }

/*------------------------------ end of file --------------------------------*/

